/*
 *  ShaderMain.cpp
 *  RTR
 *
 *  Created by Bernardo Franceschin on 4/21/11.
 *  Copyright 2011 PUC-Rio. All rights reserved.
 *
 */

#include<stdio.h>
#include<stdlib.h>
#include"glew.h"
#include"glheader.h"
#include"vshader.h"
#include"vmatrix.h"
#include"vmesh.h"
#include"vsphere.h"
#include"vtorus.h"
#include "manipulator.h"

#define _N_ 40

static VShader * shader;
static VMatrix * vmatrix;
static VMesh * bunny;
static VSphere * sphere;
static bool ellipse = true;
static VTorus  * torus;
static VGrid * flat;
//static VShape * shape;
static VGrid * shape;
static VGrid * end_shape;
static VManipulator* manip;
static bool wireframeEnabled = false;
static float * inc_normal;
static float * inc_pos;
static int step;
static bool trans = false;

static void InterpolateN (float * v1, float * v2, float* res, int n, int n_steps)
{
  for(int i=0;i<n;i++){
    res[i] = (v2[i]-v1[i])/(float)n_steps;
    //printf("%d %f\n",i,res[i]);
  }
}

static void SetTransition (VGrid * g){
 InterpolateN(shape->vertices,g->vertices,inc_pos,_N_*_N_*3,100);
 InterpolateN(shape->v_normals,g->v_normals,inc_normal,_N_*_N_*3,100);
 trans=true;
 end_shape = g;
 step=0;
 ellipse=false; 
}


void Keyboard(unsigned char key, int x, int y)
{
	switch ( key )
	{
    //case 'b':
      //shape = bunny;
      ////ellipse=false;
      //break;
    case 's':
      //shape = sphere;
      //ellipse=false;
      SetTransition(sphere);
      break;
    case 'e':
      shape = sphere;
      ellipse=true;
      break;
    case 't':
      //shape = torus;
      //ellipse=false;
      SetTransition(torus);
      break;
    case 'f':
      SetTransition(flat);
      //trans=true;
      //end_shape = flat;
      //step=0;
      //ellipse=false;
      break;
    case 'w':
      wireframeEnabled = !wireframeEnabled;
      if(wireframeEnabled)
        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
      else   glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
      break;
		case 27:
		case 'q':
		case 'Q':
			exit(0);
	}
  glutPostRedisplay();
}

void Redraw ()
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  int vp[4];
  shader->SetAsCurrent();
  if(trans){
    int p_id = shader->GetPid();
    int n_loc = glGetAttribLocation (p_id, "inc_normal");
    int p_loc = glGetAttribLocation (p_id, "inc_pos");
    int s_loc = glGetUniformLocation(p_id, "cstep");
    if(n_loc == -1 || p_loc==-1 || s_loc==-1){
      printf("inc_normal or inc_pos or step not found.\n");
      trans = false;
      shape = end_shape;
    }
    else{
      if(step>101){
        step=0;
        trans = false;
        shape = end_shape;
      }
      glEnableVertexAttribArray(p_loc);
      glEnableVertexAttribArray(n_loc);
      glUniform1f(s_loc,(float)step);
      glVertexAttribPointer(p_loc,3,GL_FLOAT,GL_FALSE,0, inc_pos);
      glVertexAttribPointer(n_loc,3,GL_FLOAT,GL_FALSE,0, inc_normal);
      printf("step = %d\n",step);
      step++;
    }
  }
  glGetIntegerv(GL_VIEWPORT,vp);
  float xmin, xmax, ymin, ymax, zmin, zmax;
  shape->GetBBox(&xmin, &xmax, &ymin, &ymax, &zmin, &zmax);
  shape->SetVertId(VPOS);
  shape->SetNormalId(VNORMAL);
  float xcenter = (xmin+xmax) / 2;
  float ycenter = (ymin+ymax) / 2;
  float zcenter = (zmin+zmax) / 2;
  float zdist = 2*(zmax-zmin);
  vmatrix->SetMode(VM_PROJECTION);
  vmatrix->LoadIdentity();
  vmatrix->Perspective(50.0f, (float)vp[2]/vp[3], 0.1f, 100.0f);
  vmatrix->SetMode(GL_MODELVIEW);
  vmatrix->LoadIdentity();
  manip->SetZCenter(zdist);
  manip->Load();
  vmatrix->LookAt(xcenter, ycenter, zcenter-zdist,
            xcenter, ycenter, zcenter,
            0.0f, 1.0f, 0.0f
           );
  
  int  l_id = glGetUniformLocation(shader->GetPid(), "lightPos");
  if(l_id == -1){
    printf("lightPos not found.\n");
  }
  //float lpos[4] = {0.0f, 10.0f, 0.0f, 1.0f};
  float lpos[4] = {0.0f, 0.0f, 0.0f, 1.0f};
  glUniform4fv(l_id, 1, lpos);
  if(ellipse)
    vmatrix->Scale(1.0,1.5,1.0);
  vmatrix->SetUniformVar(shader->GetPid());
  shape->Draw();
  glutSwapBuffers();
}

static void Init (char * vs, char* fs)
{
  GLenum err = glewInit();
  if (GLEW_OK != err) { 
    fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
    exit(-1);
  }
  glClearColor(1.0f, 1.0f, 1.0f, 1.0f); 
  //glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
  glEnable(GL_DEPTH_TEST);


  shader = new VShader();
  shader->SetVertFile(vs);
  shader->SetFragFile(fs);
  //shader->SetVertFile(PHONGVS);
  //shader->SetFragFile(PHONGFS);
  shader->CreateProgram();
  vmatrix = new VMatrix();
  //bunny = new VMesh(BUNNY);
  manip = VManipulator::Instance();
  manip->SetMatrixStack(vmatrix);
  torus = new VTorus(_N_);
  sphere = new VSphere(_N_);
  flat = new VGrid(_N_);
  flat->FillCoordAndBox();
  flat->CalcNormals();
  shape = sphere;
  inc_normal = (float*) malloc(sizeof(float)*_N_*_N_*3);
  inc_pos = (float*) malloc(sizeof(float)*_N_*_N_*3);
}

int main (int argc, char** argv)
{
  if(argc<3){
    printf("Numero insuficiente de argumentos\n");
    return 0;
  }
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGBA);
  glutInitWindowSize(800,800);
  glutCreateWindow("ShaderTest");
  glutDisplayFunc(Redraw);
  glutKeyboardFunc(Keyboard);
  Init(argv[1],argv[2]);
  glutMainLoop();
  return 0;
}
